package buildinjava.io;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

/**
 * File utility functions.
 *
 * @author Gili Tzabari
 */
public final class Files
{
	/**
	 * Prevent construction.
	 */
	private Files()
	{
	}

	/**
	 * Returns the path of one File relative to another.
	 *
	 * @param target the target directory
	 * @param base the base directory
	 * @return target's path relative to the base directory
	 * @throws IOException if an error occurs while resolving the files' canonical names
	 */
	public static File getRelativeFile(File target, File base) throws IOException
	{
		String[] baseComponents = base.getCanonicalPath().split(Pattern.quote(File.separator));
		String[] targetComponents = target.getCanonicalPath().split(Pattern.quote(File.separator));

		// skip common components
		int index = 0;
		for (; index < targetComponents.length && index < baseComponents.length; ++index)
		{
			if (!targetComponents[index].equals(baseComponents[index]))
				break;
		}

		StringBuilder result = new StringBuilder();
		if (index != baseComponents.length)
		{
			// backtrack to base directory
			for (int i = index; i < baseComponents.length; ++i)
				result.append(".." + File.separator);
		}
		for (; index < targetComponents.length; ++index)
			result.append(targetComponents[index] + File.separator);
		if (!target.isDirectory())
		{
			// remove final file separator
			result.delete(result.length() - "/".length(), result.length());
		}
		return new File(result.toString());
	}

	/**
	 * Returns all files that match a FileFilter, processing subdirectories recursively.
	 *
	 * @param directory the source directory
	 * @param stepInto indicates what directories to step into. stepInto does not need to check
	 * that the path is actually a directory.
	 * @param toReturn indicates what files should be returned. toReturn does not need to check that
	 * the path is actually a file.
	 * @return the list of files that match the filter
	 */
	public static List<File> list(File directory, FileFilter stepInto, FileFilter toReturn)
	{
		if (!directory.isDirectory())
		{
			throw new IllegalArgumentException("\"directory\" must be a directory: " + directory.
				getAbsolutePath());
		}
		return listImpl(directory, FileFilters.intersection(FileFilters.isDirectory(), stepInto),
			FileFilters.intersection(FileFilters.isFile(), toReturn));
	}

	/**
	 * Returns all files that match a FileFilter, processing subdirectories recursively.
	 *
	 * @param directory the source directory
	 * @param stepInto indicates what directories to step into
	 * @param toReturn indicates what files should be returned
	 * @return the list of files that match the filter
	 */
	private static List<File> listImpl(File directory, FileFilter stepInto, FileFilter toReturn)
	{
		if (!directory.isDirectory())
		{
			throw new IllegalArgumentException("stepInto accepted a non-directory: " + directory.
				getAbsolutePath());
		}
		List<File> result = Lists.newArrayList();
		for (File child: directory.listFiles(stepInto))
			result.addAll(listImpl(child, stepInto, toReturn));
		result.addAll(Arrays.asList(directory.listFiles(toReturn)));
		return result;
	}

	/**
	 * Converts a path to a class name.
	 *
	 * @param path the path of the class file
	 * @param rootPackage the path of the root package
	 * @return the class name
	 * @throws IllegalArgumentException if path does not refer to a class file
	 * @throws IOException if an I/O error occurs while invoking File.getCanonicalFile()
	 */
	public static String getClassName(File path, File rootPackage) throws IllegalArgumentException,
																																				IOException
	{
		String result = getRelativeFile(path, rootPackage).getPath().replace("\\", "/").
			replace("/", ".");
		return result.substring(0, result.length() - ".class".length());
	}

	/**
	 * Returns a function that converts a File to a URL.
	 *
	 * @return a function that converts a File to a URL
	 */
	public static Function<File, URL> toURL()
	{
		return new Function<File, URL>()
		{
			@Override
			public URL apply(File from)
			{
				try
				{
					return from.toURI().toURL();
				}
				catch (MalformedURLException e)
				{
					// Thrown if the protocol handler for file:// could not be found
					throw new AssertionError(e);
				}
			}
		};
	}
}
